//单例模式 （Singleton Pattern）又称为单体模式，保证一个类只有一个实例，并提供一个访问它的全局访问点。
//也就是说，第二次使用同一个类创建新对象的时候，应该得到与第一次创建的对象完全相同的对象。



//1、每次访问者来访问，返回的都是同一个实例；
//如果一开始实例没有创建，那么这个特定类需要自行创建这个实例；

//2、代码实现
function ManageGame() {
    if (ManageGame._schedule) {        // 判断是否已经有单例了
        return ManageGame._schedule
    }
    ManageGame._schedule = this
}

ManageGame.getInstance = function () {
    if (ManageGame._schedule) {        // 判断是否已经有单例了
        return ManageGame._schedule
    }
    return ManageGame._schedule = new ManageGame()
}

const schedule1 = new ManageGame()
const schedule2 = ManageGame.getInstance()

//用es6改造
class ManageGame {
    static _schedule = null

    static getInstance() {
        if (ManageGame._schedule) {        // 判断是否已经有单例了
            return ManageGame._schedule
        }
        return ManageGame._schedule = new ManageGame()
    }

    constructor() {
        if (ManageGame._schedule) {        // 判断是否已经有单例了
            return ManageGame._schedule
        }
        ManageGame._schedule = this
    }
}

//上面方法的缺点在于维护的实例作为静态属性直接暴露，外部可以直接修改。



//3、通用实现
//3.1使用立即调用函数 IIFE 将不希望公开的单例实例 instance 隐藏。
const Singleton = (function () {
    let _instance = null        // 存储单例

    const Singleton = function () {
        if (_instance) return _instance     // 判断是否已有单例
        _instance = this
        this.init()                         // 初始化操作
        return _instance
    }

    Singleton.prototype.init = function () {
        this.foo = 'Singleton Pattern'
    }

    Singleton.getInstance = function () {
        if (_instance) return _instance
        _instance = new Singleton()
        return _instance
    }

    return Singleton
})()
//这样一来，虽然仍使用一个变量 _instance 来保存单例，但是由于在闭包的内部，所以外部代码无法直接修改。
//代价和上例一样是闭包开销，并且因为 IIFE 操作带来了额外的复杂度，让可读性变差。
//IIFE 内部返回的 Singleton 才是我们真正需要的单例的构造函数，外部的 Singleton 把它和一些单例模式的创建逻辑进行了一些封装。


//3.2  ES6 的 let/const 之后，可以通过 { } 块级作用域的方式来隐藏内部变量：
let getInstance

{
    let _instance = null        // 存储单例

    const Singleton = function () {
        if (_instance) return _instance     // 判断是否已有单例
        _instance = this
        this.init()                         // 初始化操作
        return _instance
    }

    Singleton.prototype.init = function () {
        this.foo = 'Singleton Pattern'
    }

    getInstance = function () {
        if (_instance) return _instance
        _instance = new Singleton()
        return _instance
    }
}

const visitor1 = getInstance()
const visitor2 = getInstance()


//之前的例子中，单例模式的创建逻辑和原先这个类的一些功能逻辑（比如 init 等操作）混杂在一起，根据单一职责原则，
//这个例子我们还可以继续改进一下，将单例模式的创建逻辑和特定类的功能逻辑拆开，这样功能逻辑就可以和正常的类一样。

/* 功能类 */
class FuncClass {
    constructor(bar) {
        this.bar = bar
        this.init()
    }

    init() {
        this.foo = 'Singleton Pattern'
    }
}

/* 单例模式的赋能类 */
const Singleton = (function () {
    let _instance = null        // 存储单例

    const ProxySingleton = function (bar) {
        if (_instance) return _instance     // 判断是否已有单例
        _instance = new FuncClass(bar)
        return _instance
    }

    ProxySingleton.getInstance = function (bar) {
        if (_instance) return _instance
        _instance = new Singleton(bar)
        return _instance
    }

    return ProxySingleton
})()

const visitor1 = new Singleton('单例1')
const visitor2 = new Singleton('单例2')
const visitor3 = Singleton.getInstance()

//这样的单例模式赋能类也可被称为代理类，将业务类和单例模式的逻辑解耦，把单例的创建逻辑抽象封装出来，有利于业务类的扩展和维护。


//使用类似的概念，配合 ES6 引入的 Proxy 来拦截默认的 new 方式，我们可以写出更简化的单例模式赋能方法：

/* Person 类 */
class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}

/* 单例模式的赋能方法 */
function Singleton(FuncClass) {
    let _instance
    return new Proxy(FuncClass, {
        construct(target, args) {
            return _instance || (_instance = Reflect.construct(FuncClass, args)) // 使用 new FuncClass(...args) 也可以
        }
    })
}

const PersonInstance = Singleton(Person)

const person1 = new PersonInstance('张小帅', 25)
const person2 = new PersonInstance('李小美', 23)

console.log(person1 === person2)	// true


//4. 惰性单例、懒汉式-饿汉式
//懒汉式单例是在使用时才实例化
//饿汉式是当程序启动时或单例模式类一加载的时候就被创建。
class FuncClass {
    constructor() { this.bar = 'bar' }
}

// 饿汉式
const HungrySingleton = (function () {
    const _instance = new FuncClass()

    return function () {
        return _instance
    }
})()

// 懒汉式
const LazySingleton = (function () {
    let _instance = null

    return function () {
        return _instance || (_instance = new FuncClass())
    }
})()

const visitor1 = new HungrySingleton()
const visitor2 = new HungrySingleton()
const visitor3 = new LazySingleton()
const visitor4 = new LazySingleton()

console.log(visitor1 === visitor2)	// true
console.log(visitor3 === visitor4)	// true


//5. 源码中的单例模式
//以 ElementUI 为例，ElementUI 中的全屏 Loading 蒙层调用有两种形式：

// 1. 指令形式
Vue.use(Loading.directive)
// 2. 服务形式
Vue.prototype.$loading = service
//上面的是指令形式注册，使用的方式      <div :v-loading.fullscreen="true">...</div>；
//下面的是服务形式注册，使用的方式        this.$loading({ fullscreen: true })；
//用服务方式使用全屏 Loading 是单例的，即在前一个全屏 Loading 关闭前再次调用全屏 Loading，并不会创建一个新的 Loading 实例，
//而是返回现有全屏 Loading 的实例。


//下面我们可以看看 ElementUI 2.9.2 的源码是如何实现的，为了观看方便，省略了部分代码：
import Vue from 'vue'
import loadingVue from './loading.vue'

const LoadingConstructor = Vue.extend(loadingVue)

let fullscreenLoading

const Loading = (options = {}) => {
    if (options.fullscreen && fullscreenLoading) {
        return fullscreenLoading
    }

    let instance = new LoadingConstructor({
        el: document.createElement('div'),
        data: options
    })

    if (options.fullscreen) {
        fullscreenLoading = instance
    }
    return instance
}

export default Loading
//这里的单例是 fullscreenLoading，是存放在闭包中的，如果用户传的 options 的 fullscreen 为 true
//且已经创建了单例的情况下则回直接返回之前创建的单例，如果之前没有创建过，则创建单例并赋值给闭包中的 fullscreenLoading 后
//返回新创建的单例实例。

//这是一个典型的单例模式的应用，通过复用之前创建的全屏蒙层单例，不仅减少了实例化过程，而且避免了蒙层叠加蒙层出现的底色变深的情况。




// 6. 单例模式的优缺点
// 单例模式主要解决的问题就是节约资源，保持访问一致性。

// 简单分析一下它的优点：

// 单例模式在创建后在内存中只存在一个实例，节约了内存开支和实例化时的性能开支，特别是需要重复使用一个创建开销比较大的类时，
//比起实例不断地销毁和重新实例化，单例能节约更多资源，比如数据库连接；
// 单例模式可以解决对资源的多重占用，比如写文件操作时，因为只有一个实例，可以避免对一个文件进行同时操作；
// 只使用一个实例，也可以减小垃圾回收机制 GC（Garbage Collecation） 的压力，表现在浏览器中就是系统卡顿减少，
//操作更流畅，CPU 资源占用更少；
// 单例模式也是有缺点的

// 单例模式对扩展不友好，一般不容易扩展，因为单例模式一般自行实例化，没有接口；
// 与单一职责原则冲突，一个类应该只关心内部逻辑，而不关心外面怎么样来实例化；
// 7. 单例模式的使用场景
// 那我们应该在什么场景下使用单例模式呢：

// 当一个类的实例化过程消耗的资源过多，可以使用单例模式来避免性能浪费；
// 当项目中需要一个公共的状态，那么需要使用单例模式来保证访问一致性；